package cmd

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gcmd"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"

	"rewords-server-2024/internal/model/entity"
)

var (
	AddWord = &gcmd.Command{
		Name:        "addword",
		Brief:       "Import words from JSON files into MongoDB",
		Description: "Import words from JSON files into MongoDB. You can specify either a single JSON file or a directory containing multiple JSON files.",
		Arguments: []gcmd.Argument{
			{
				Name:  "path",
				Short: "p",
				Brief: "Input file or directory path",
				IsArg: false,
			},
		},
		Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
			// 从命令行参数获取路径
			inputPath := parser.GetOpt("path").String()
			if inputPath == "" {
				return fmt.Errorf("please specify an input path using --path flag")
			}

			// 从配置文件获取MongoDB连接信息
			mongoUri := g.Cfg().MustGet(ctx, "mongoConf.mongoUrl").String()
			dbName := g.Cfg().MustGet(ctx, "mongoConf.mgoDbName").String()

			// 连接MongoDB
			clientOpts := options.Client().ApplyURI(mongoUri)
			client, err := mongo.Connect(ctx, clientOpts)
			if err != nil {
				return fmt.Errorf("failed to connect to MongoDB: %v", err)
			}
			defer client.Disconnect(ctx)

			// 测试连接
			if err := client.Ping(ctx, nil); err != nil {
				return fmt.Errorf("failed to ping MongoDB: %v", err)
			}

			// 获取数据库和集合
			db := client.Database(dbName)
			collection := db.Collection("word_dict")

			// 检查是否是目录或文件
			fileInfo, err := os.Stat(inputPath)
			if err != nil {
				return fmt.Errorf("error accessing input path: %v", err)
			}

			if fileInfo.IsDir() {
				// 处理目录中的所有JSON文件
				files, err := os.ReadDir(inputPath)
				if err != nil {
					return fmt.Errorf("error reading directory: %v", err)
				}

				for _, file := range files {
					if !file.IsDir() && strings.HasSuffix(file.Name(), ".json") {
						filePath := filepath.Join(inputPath, file.Name())
						if err := processFile(ctx, collection, filePath); err != nil {
							g.Log().Errorf(ctx, "Error processing file %s: %v", filePath, err)
						}
					}
				}
			} else {
				// 处理单个文件
				if err := processFile(ctx, collection, inputPath); err != nil {
					return fmt.Errorf("error processing file: %v", err)
				}
			}

			return nil
		},
	}
)

func processFile(ctx context.Context, collection *mongo.Collection, filePath string) error {
	// 打开文件
	file, err := os.Open(filePath)
	if err != nil {
		return fmt.Errorf("error opening file: %v", err)
	}
	defer file.Close()

	// 创建一个scanner来逐行读取
	scanner := bufio.NewScanner(file)

	// 设置较大的buffer size，因为每行可能很长
	const maxCapacity = 1024 * 1024 // 1MB
	buf := make([]byte, maxCapacity)
	scanner.Buffer(buf, maxCapacity)

	// 逐行处理
	lineNum := 0
	var words []interface{} // 用于批量插入
	batchSize := 100        // 每100个单词批量插入一次

	for scanner.Scan() {
		lineNum++
		line := scanner.Text()

		// 解析JSON行
		var wordDict entity.WordDict
		if err := json.Unmarshal([]byte(line), &wordDict); err != nil {
			g.Log().Errorf(ctx, "Error parsing JSON at line %d in file %s: %v", lineNum, filePath, err)
			continue
		}

		// 添加到批处理数组
		words = append(words, wordDict)

		// 当达到批处理大小时，执行插入
		if len(words) >= batchSize {
			if err := insertWords(ctx, collection, words); err != nil {
				g.Log().Errorf(ctx, "Error batch inserting words from file %s: %v", filePath, err)
			}
			words = words[:0] // 清空数组但保留容量
		}
	}

	// 插入剩余的单词
	if len(words) > 0 {
		if err := insertWords(ctx, collection, words); err != nil {
			g.Log().Errorf(ctx, "Error batch inserting remaining words from file %s: %v", filePath, err)
		}
	}

	// 检查scanner是否有错误
	if err := scanner.Err(); err != nil {
		return fmt.Errorf("error reading file %s: %v", filePath, err)
	}

	g.Log().Infof(ctx, "Successfully processed %d words from file: %s", lineNum, filepath.Base(filePath))
	return nil
}

func insertWords(ctx context.Context, collection *mongo.Collection, words []interface{}) error {
	if len(words) == 0 {
		return nil
	}

	// 批量插入
	_, err := collection.InsertMany(ctx, words)
	return err
}

func init() {
	Main.AddCommand(AddWord)
}
